/*  
  -- The Lime II Project -- 

  A tuplespaces-based middleware for coordinating agents and mobile hosts.
  Copyright (C) 2005.
  Gian Pietro Picco, Amy L. Murphy, Lorenzo Bellini.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.

  You should have received this copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
*/

package lime2;

/**
 * A remote op that deals with fragmentation. 
 * <br>
 * FragmentOp can be used in two ways: as a facility for checking whether a remote op has to be fragmented (a wrapper
 * FragmentOp) or as a concrete fragment. These two functions do not overlap: i.e. you can extract the fragments from
 * the wrapper FragmentOp, not vice-versa. 
 * <br>
 * The two functions would overlap only if recursive fragmentation would be allowed; but it is not allowed since it is
 * not necessary. 
 * 
 * @author Gian Pietro Picco
 * @author Amy Murphy
 * @author Lorenzo Bellini
 */

class FragmentOp extends RemoteOp
{	
 // private data members	
 private boolean wrapper;           // identifies the wrapper FragmentOp wrt real fragments  
 private boolean recop;             // identifies fragments of RemReconcileOps
 private byte[] bytes;              // (fraction of) serialized remote op  
 transient private int granularity;	// calibrates size limit
  
 // non-private data members
 int fragments; // total number of fragments the original remote op has been splitted into
 int offset;    // fragment number
 
 /**
  * Constructor method. Wraps the (serialized form) of the original remote op. Initializes granularity so that 
  * fragments will not exceed the size limit defined within LimeConstants. 
  * 
  * @param ro remote op to wrap
  * @throws FragmentationException
  */
 FragmentOp(RemoteOp ro) throws FragmentationException
 {
  super(LimeConstants.FRAGMENT, ro.name);	
  this.copyOriginal(ro);
  this.wrapper = true;
  this.recop = (ro.type == LimeConstants.RECONCILE ? true : false);
  this.bytes = Serializer.serialize(ro);
  this.granularity = LimeConstants.SIZE_LIMIT - Serializer.serialize(new FragmentOp(this, new byte[0], 0, 0)).length;
  if (granularity <= 0) throw new FragmentationException("size limit must be greater than header-only remote op (" + (-granularity) + ")");
  this.fragments = (bytes.length / granularity) + (bytes.length % granularity != 0 ? 1 : 0);
  this.offset = 0;
 }
 
 /**
  * Constructor method for creating a fragment. Private.
  * 
  * @param dad wrapper FragmentOp (.. just to pass header information)
  * @param bytes the serialized op to split
  * @param fragments the total number of fragments the original remote op has been splitted into
  * @param offset the offset number of this fragment (0 <= offset < fragments)
  */
 private FragmentOp(FragmentOp dad, byte[] bytes, int fragments, int offset)
 {
  super(LimeConstants.FRAGMENT, dad.name);
  this.copyOriginal(dad);
  this.wrapper = false;
  this.recop = dad.recop;
  this.bytes = bytes;
  this.fragments = fragments;
  this.offset = offset;
 }
 
 /**
  * Tells whether the remote op passed to the wrapper FragmentOp requires fragmentation or not.
  * 
  * @return <code>true</code> or <code>false</code>
  * @throws FragmentationException
  */
 boolean tooBig() throws FragmentationException
 {
  if (!wrapper) throw new FragmentationException("fragments always respect the size limit");	
  if (bytes.length >= LimeConstants.SIZE_LIMIT) return true; else return false;	
 }
 
 /**
  * Tells whether this FragmentationOp comes from a RemReconcileOp.
  * 
  * @return <code>true</code> or <code>false</code>
  */
 boolean isReconcileOp()
 {
  return recop;	
 }
 
 /**
  * Tells whether the are more fragments for this wrapper FragmentOp.
  * 
  * @return <code>true</code> or <code>false</code>
  * @throws FragmentationException
  */
 boolean hasFragment() throws FragmentationException
 {
  if (!wrapper) throw new FragmentationException("recursive fragmentation is not allowed");	
  if (offset * granularity < bytes.length) return true; else return false;	
 }
 
 /**
  * Returns the next fragment of this wrapper FragmentOp.
  * 
  * @return next fragment
  * @throws FragmentationException
  */
 FragmentOp nextFragment() throws FragmentationException
 {	
  if (!wrapper) throw new FragmentationException("recursive fragmentation is not allowed");	
  int pos = offset * granularity;
  if (pos >= bytes.length) return null;
  int many = bytes.length - pos;	
  if (many > granularity) many = granularity; 
  byte[] tmp = new byte[many];
  for (int i = 0; i < many; i++) tmp[i] = bytes[pos + i]; 
  return new FragmentOp(this, tmp, fragments, offset++);
 } 

 /**
  * Merges two fragments. The fragment passed as a parameter should be the consecutive one wrt to this fragment.  
  * 
  * @param next fragment to merge (consecutive)
  * @throws FragmentationException
  */
 void mergeFragment(FragmentOp next) throws FragmentationException
 {	
  if (wrapper || next.wrapper) throw new FragmentationException("only fragments can be merged");
  byte[] tmp = new byte[bytes.length + next.bytes.length];
  for (int i = 0; i < bytes.length; i++) tmp[i] = bytes[i];
  for (int i = 0; i < next.bytes.length; i++) tmp[i + bytes.length] = next.bytes[i];
  bytes = tmp;
 }
  
 /**
  * Re-assembles a RemoteOp (must be the result of the merging of ALL fragments).
  * 
  * @return re-assembled RemoteOp
  * @throws FragmentationException
  */
 RemoteOp reAssemble() throws FragmentationException
 {	
  try { return (RemoteOp) Serializer.deserialize(bytes); }
  catch (RuntimeException re) { throw new FragmentationException("defragmentation fault"); }  
 }
 
 /**
  * Tests two fragments for equality.
  * 
  * @param o Object
  * @return <code>true</code> if the fragments match, <code>false</code> otherwise 
  */ 
 public boolean equals(Object o)
 {
  boolean ret = false;	
  if (o instanceof FragmentOp)
  {
   FragmentOp ff = (FragmentOp) o;
   if (ti == ff.ti && seqnum == ff.seqnum && offset == ff.offset && source.equals(ff.source)) ret = true;
  }
  return ret;
 }
 
 // copies the header of the given remote op into this one (except field "name" and "type" of course). Shallow copy
 private void copyOriginal(RemoteOp ro)
 {
  this.subtype = ro.subtype;
  this.sourceT = ro.sourceT;
  this.targetT = ro.targetT;
  this.source = ro.source;
  this.target = ro.target;
  this.ti = ro.ti;
  this.seqnum = ro.seqnum;
 }
 
 /**
  * toString method.
  * 
  * @return textual representation 
  */ 
 public String toString() 
 {
  return "FragmentOp (" + Translator.translateSubtype(subtype) + ", " + (offset + 1) + "/" + fragments + ")";	
 }
 
}